<?php
/**
 * Bureaucracy Plugin Helper: Creates forms and submits them via email
 *
 * @license    GPL 2 (http://www.gnu.org/licenses/gpl.html)
 * @author     Andreas Gohr <andi@splitbrain.org>
 * @author     Stephane Chamberland <dokuwiki@meteokid.ca>
 * @date       2009-08-01
 */

// must be run within Dokuwiki
if (!defined('DOKU_INC')) die();

if (!defined('DOKU_LF')) define('DOKU_LF', "\n");
if (!defined('DOKU_TAB')) define('DOKU_TAB', "\t");

if(!defined('DOKU_PLUGIN')) define('DOKU_PLUGIN',DOKU_INC.'lib/plugins/');
require_once(DOKU_PLUGIN.'bureaucracy2/form_element.php');

/**
 *
 */
class helper_plugin_bureaucracy2 extends DokuWiki_Plugin {

    //TODO: add secutiry as
    //http://www.dokuwiki.org/devel:plugin_programming_tips#making_your_plugins_more_secure
    //http://www.dokuwiki.org/devel:security


    var $form_prefix = 'buro__';

    /**
     *
     */
    function getInfo() {
        return array(
                'author' => 'Andreas Gohr, Stephane Chamberland',
                'email'  => 'andi@splitbrain.org',
                'date'   => '2009-08-01',
                'name'   => 'Bureaucracy Plugin (helper class)',
                'desc'   => 'Functions to help form generator/emailer',
                'url'    => 'http://wiki.splitbrain.org/plugin:bureaucracy2',
                );
    }

    //TODO: update
    /**
     *
     */
    function getMethods() {
        $result = array();
    }

    /**
     * Parse a line into (quoted) arguments
     * @author William Fletcher <wfletcher@applestone.co.za>
     * @input line: string - string to parse/split
     * @return array - split list of (quoted) args
     */
    function _parse_line($line) {
        $args = array();
        $inQuote = false;
        $len = strlen($line);
        for(  $i = 0 ; $i <= $len; $i++ ) {
            if( $line{$i} == '"' ) {
                if($inQuote) {
                    array_push($args, $arg);
                    $inQuote = false;
                    $arg = '';
                    continue;
                } else {
                    $inQuote = true;
                    continue;
                }
            } else if ( $line{$i} == ' ' ) {
                if($inQuote) {
                    $arg .= ' ';
                    continue;
                } else {
                    if ( strlen($arg) < 1 ) continue;
                    array_push($args, $arg);
                    $arg = '';
                    continue;
                }
            }
            $arg .= $line{$i};
        }
        if ( strlen($arg) > 0 ) array_push($args, $arg);
        return $args;
    }

    /**
     * Parse bureaucracy from desc string
     * @input formDef: string - form elements definition
     * @return array -
     */
    function _parse_form_def($formDef){
        //TODO: may want to sanityze/clean $formDef from potential malicious code
        $lines = explode("\n",$formDef);
        $idx = 0;
        // parse the lines into a command/argument array
        $thanks = '';
        $name = $this->form_prefix;
        $show = '';
        $actions = array();
        $elements = array();
        foreach($lines as $line){

            $line = trim($line);
            if(!$line) continue;
            $args = $this->_parse_line($line);
            $cmd = strtolower(array_shift($args));

            switch($cmd){
                case 'thanks':
                    $thanks = $args[1]; //TODO: may want to clean text
                    continue;
                case 'name':
                    $name = cleanID($args[1],false,true).'__';
                    $this->form_prefix = $name;
                    continue;
                case 'show':
                    $show = $args[1]; //TODO: may want to check valid values
                    continue;
                case 'action':
                    //TODO: check that action exists
                    //TODO: keep action args
                    $atype = array_shift($args);
                    $actions[] = array('type'=>$atype,'argv'=>$args);
                    continue;
            }

            //TODO: for security reasons, may want to check against allowed class
            $elm_class_name = 'helper_plugin_bureaucracy2_element_'.$cmd;
            if(!class_exists($elm_class_name)){
                msg(sprintf($this->getLang('e_unknowntype'),hsc($cmd)),-1);
                continue;
            }
            $elm = new $elm_class_name();
            if(count($args) < $elm->args_min){
                msg(sprintf($this->getLang('e_missingargs'),hsc($cmd),hsc($args[0])),-1);
                continue;
            }

            $err = $elm->parse_args($args);
            if($err){
                unset($elm);
                //TODO: show the error
            }else{
                $elements[] = $elm;
            }

            //TODO: parse an optional layout section containing wiki syntax
        }

        return array('elements'=>$elements,'actions'=>$actions,'thanks'=>$thanks,'name'=>$name,'show'=>$show);
    }

    /**
     * Create the form
     * @input data: array [elements,action,thanks,show]
     *        - elements: array - form element objects
     *        - actions: array - list of action.php scripts to run on submit success
     *        - thanks: string - msg to display on action sucess
     *        - show: string - if == always, show form even after action
     *        - name: string - form name prefix (default to $this->from_perfix
     * @input errors: array - list form element errors (after submit)
     * @input reset: bool (optional) - use default values if set to true
     * @input forced_post: array (optional) - use provided values instead of $_POST
     * @return string - HTML code for form
     */
    //TODO: pass the whole data array so we have name (no need to use $this->form_prefix
    function _htmlform($data,$errors,$reset=false,$forced_post=NULL){
        global $ID;

        $form = new Doku_Form('bureaucracy__plugin',wl($ID).'#'.$this->form_prefix,'post');
        $form->addHidden('id',$ID);

        $captcha = false; // to make sure we add it only once

        if(is_null($forced_post)) $forced_post = $_POST;
        $values = NULL;
        if(is_array($forced_post[$data['name']])) $values = $forced_post[$data['name']];
        foreach($data['elements'] as $elm){
            $elm->htmlform_elm($form,$values,$errors,$reset);
        }

        ob_start();
        echo '<a name="'.hsc($this->form_prefix).'"></a>';
        $form->printForm();
        $out .= ob_get_contents();
        ob_end_clean();
        return $out;
    }

    /**
     * Validate any posted data, display errors using the msg() function,
     * put a list of bad fields in the return array
     * @input data: array [elements,action,thanks,show]
     *        - elements: array - form element objects
     *        - actions: array - list of action.php scripts to run on submit success
     *        - thanks: string - msg to display on action sucess
     *        - show: string - if == always, show form even after action
     *        - name: string - form name prefix (default to $this->from_perfix
     * @return array - list of elements name with errors
     */
    function _checkpost(&$data,$forced_post=NULL){
        if(is_null($forced_post)) $forced_post = $_POST;
        $values = array();
        if(is_array($forced_post[$data['name']])) $values = $forced_post[$data['name']];
        $errors = array();
        foreach($data['elements'] as $elm){
            $err = $elm->checkpost($values);
            if($err) $errors[$elm->id] = $err;
        }
        return $errors;
    }

    /**
     * Render the Form and/or action result
     * @input data: array [elements,action,thanks,show]
     *        - elements: array - form element objects
     *        - actions: array - list of action.php scripts to run on submit success
     *        - thanks: string - msg to display on action sucess
     *        - show: string - if == always, show form even after action
     *        - name: string - form name prefix (default to $this->from_perfix
     * @return string - HTML code for form or post action HTML result
     */
    function _renderHTML($data) {
        $errors = array();
        $reset = true;
        if(isset($_POST['bureaucracy__'.$data['name']])){
            $errors = $this->_checkpost($data);

            // check CAPTCHA
            $ok = true;
            $helper = plugin_load('helper','captcha');
            if(!is_null($helper) && $helper->isEnabled()){
                $ok = $helper->check();
            }

            $reset = false;
            $doc = '';

            //TODO: move action call into own function
            //TODO: allow muliple actions
            //TODO: need to review action to use elements instead of data
            /*
            if($ok && !count($errors) && $data['action']){
                require_once(DOKU_PLUGIN . 'bureaucracy2/actions/actions.php');
                require_once(DOKU_PLUGIN . 'bureaucracy2/actions/' . $data['action']['type'] . '.php');
                $class = 'syntax_plugin_bureaucracy2_action_' . $data['action']['type'];
                $action = new $class();

                $success = $action->run($data['data'], $data['thanks'], $data['action']['argv'], $errors);
                if($success) {
                    $doc .= '<div class="bureaucracy__plugin" id="scroll__here">';
                    $doc .= $success;
                    $doc .= '</div>';
                    $reset = true;
                }
            }
            */
        }

        $showform = false;
        if (isset($data['show']) && $data['show'] == 'always') $showform = true;
        if (!$doc || $showform) $doc .= $this->_htmlform($data,$errors,$reset);
        return $doc;
    }

    /**
     * Collect posted form data in an k=>v array
     * will keep only element in $_POST defined in the $elements
     * unless keepPOST and/or keepGET is true
     * @input elements: array - form element objects
     * @input keepPOST: bool - keep $_POST array keys
     * @input keepGET: bool -  keep $_GET array keys
     */
    function _collectpost($elements,$keepPOST=false,$keepGET=false){
        $values = array();
        $allkeys = array();
        //TODO: clean $_POST, $_GET array elements (k && v)
        if ($keepPOST) $allkeys = array_merge($allkeys,$_POST);
        if ($keepGET)  $allkeys = array_merge($allkeys,$_GET);
        $name = $this->form_prefix;
        foreach ($elements as $elm) {
            $kv = $elm->get_submit_key_val(); //TODO: make sure $kv is cleaned
            if($kv) {
                $values = array_merge($values,$kv);
                $k = array_keys($kv);
                if(isset($allkeys[$name][$k[0]])) unset($allkeys[$name][$k[0]]);
            }
        }
        $values = array_merge($allkeys,$values);
        return $values;
    }

}

















/**
 *
 */
class helper_plugin_bureaucracy0 extends DokuWiki_Plugin {

    var $form_prefix = 'buro__';

    //minimal number of args for each type [+1]
    var $argcheck = array(
                        'textbox'    => 2,
                        'email'      => 2,
                        'date'       => 2,
                        'phone'      => 2,
                        'password'   => 2,
                        'number'     => 2,
                        'submit'     => 1,
                        'fieldset'   => 1,
                        'select'     => 3,
                        'onoff'      => 2,
                        'yesno'      => 2,
                        'leftcheck'  => 2,
                        'static'     => 2,
                        'textarea'   => 2,
                        'action'     => 2,
                        'thanks'     => 2,
                        'name'       => 2,
                        'show'       => 2,
                        'const'      => 3,
                        'link'      => 2,
                        'file'      => 2
                        );
    // types that are no fields
    var $nofield = array('action','static','fieldset','thanks','name','show');

    var $classes = array(
            'optional' => 'bureaucracy_optional',
            'required' => 'bureaucracy_required',
            'error'    => 'bureaucracy_error'
            );

    function getInfo() {
        return array(
                'author' => 'Andreas Gohr, Stephane Chamberland',
                'email'  => 'andi@splitbrain.org',
                'date'   => '2009-02-07',
                'name'   => 'Bureaucracy Plugin (helper class)',
                'desc'   => 'Functions to help form generator/emailer',
                'url'    => 'http://wiki.splitbrain.org/plugin:bureaucracy',
                );
    }

   //TODO: update
    function getMethods() {
        $result = array();
        $result[] = array(
                'name'   => '_file_isvalid',
                'desc'   => 'Very minimal file validity check',
                'params' => array(
                    'datestr' => 'string'
                    ),
                'return' => array('ok' => 'true/false'),
                );
        $result[] = array(
                'name'   => '_link_isvalid',
                'desc'   => 'Very minimal link validity check',
                'params' => array(
                    'datestr' => 'string'
                    ),
                'return' => array('ok' => 'true/false'),
                );
        $result[] = array(
                'name'   => '_date_isvalid',
                'desc'   => 'Very minimal date validity check',
                'params' => array(
                    'datestr' => 'string'
                    ),
                'return' => array('ok' => 'true/false'),
                );
        $result[] = array(
                'name'   => '_phone_isvalid',
                'desc'   => 'Very minimal phone number validity check',
                'params' => array(
                    'phonestr' => 'string'
                    ),
                'return' => array('ok' => 'true/false'),
                );
        $result[] = array(
                'name'   => '_parse_line',
                'desc'   => 'Parse a line into (quoted) arguments',
                'params' => array(
                    'line' => 'string'
                    ),
                'return' => array('args' => 'array'),
                );
        $result[] = array(
                'name'   => '_parse_form_def',
                'desc'   => 'Parse bureaucracy from desc string',
                'params' => array(
                    'formDesc' => 'string'
                    ),
                'return' => array('data' => 'array'),
                );
        $result[] = array(
                'name'   => '_htmlform',
                'desc'   => 'Create the form',
                'params' => array(
                    'data'   => 'array',
                    'errors' => 'array'
                    ),
                'return' => array('out' => 'string'),
                );
        $result[] = array(
                'name'   => '_checkpost',
                'desc'   => 'Validate any posted data, display errors using the msg() function, put a list of bad fields in the return array',
                'params' => array(
                    'data' => 'array'
                    ),
                'return' => array('errors' => 'array'),
                );
        $result[] = array(
                'name'   => '_collectpost',
                'desc'   => 'Collect posted form data in an k=>v array',
                'params' => array(
                    'data' => 'array'
                    ),
                'return' => array('values' => 'array'),
                );
        $result[] = array(
                'name'   => '_action',
                'desc'   => 'Call bureaucracy action on submit or render form',
                'params' => array(
                    'data' => 'array'
                    ),
                'return' => array('out' => 'string'),
                );
         return $result;
    }

    /**
     * Very minimal file validity check
     */
    function _file_isvalid($filestr) {
        $filename = mediaFN($filestr);
        if (@file_exists($filename)) return true;
        return false;
    }


    /**
     * very minimal link validity check
     * Warning: use with caution as it may be a security issue
     * url_regexp1 from (not working for some reasons... to investigate):
     *   https://www.owasp.org/index.php/OWASP_Validation_Regex_Repository
     * url_regexp2 from:
     *   http://stackoverflow.com/questions/206059/php-validationregex-for-url
     * would be best to use the PHP filter_var function
     * but it's limited to PHP 5 >= 5.2.0
     */
    function _link_isvalid($urlstr) {
        //$url_regexp1 = "/^((((https?|ftps?|gopher|telnet|nntp):\/\/)|(mailto:|news:))(%[0-9A-Fa-f]{2}|[-()_.!~*';\/?:@&=+$,A-Za-z0-9])+)([).!';\/?:,]blank:)?$/i";
        $url_regexp2 = "/\b(?:(?:https?|ftp):\/\/|www\.)[-a-z0-9+&@#\/%?=~_|!:,.;]*[-a-z0-9+&@#\/%=~_|]/i";
        if (preg_match($url_regexp2, $urlstr)) return true;
        return false;
    }

    /**
     * Very minimal date validity check
     */
    function _date_isvalid($datestr) {
        $datestamp = strtotime($datestr);
        if ($datestamp && $datestamp > 0) return true;
        return false;
    }

    /**
     * Very minimal phone number validity check
     */
    function _phone_isvalid($phonestr) {
        //if(!ereg('^([1]-)?[0-9]{3}-[0-9]{3}-[0-9]{4}$', $phone)){  //
        $v2 = str_replace(array('+','(',')','-','.',' '),array('','','','','',''),trim($phonestr));
        $l = strlen($v2);
        if (is_numeric($v2) && $l>=10 && $l <=13) return true;
        return false;
    }

    /**
     * Parse a line into (quoted) arguments
     *
     * @author William Fletcher <wfletcher@applestone.co.za>
     */
    function _parse_line($line) {
        $args = array();
        $inQuote = false;
        $len = strlen($line);
        for(  $i = 0 ; $i <= $len; $i++ ) {
            if( $line{$i} == '"' ) {
                if($inQuote) {
                    array_push($args, $arg);
                    $inQuote = false;
                    $arg = '';
                    continue;
                } else {
                    $inQuote = true;
                    continue;
                }
            } else if ( $line{$i} == ' ' ) {
                if($inQuote) {
                    $arg .= ' ';
                    continue;
                } else {
                    if ( strlen($arg) < 1 ) continue;
                    array_push($args, $arg);
                    $arg = '';
                    continue;
                }
            }
            $arg .= $line{$i};
        }
        if ( strlen($arg) > 0 ) array_push($args, $arg);
        return $args;
    }

    /**
     * Parse bureaucracy from desc string
     */
    function _parse_form_def($formDef){
        $lines = explode("\n",$formDef);
        $action = array('type'=>'','argv'=>array());
        $thanks = '';

        $idx = 0;
        // parse the lines into an command/argument array
        $cmds = array();
        foreach($lines as $line){
            $line = trim($line);
            if(!$line) continue;
            $args = $this->_parse_line($line);
            $args[0] = strtolower($args[0]);
            if(!isset($this->argcheck[$args[0]])){
                msg(sprintf($this->getLang('e_unknowntype'),hsc($args[0])),-1);
                continue;
            }
            if(count($args) < $this->argcheck[$args[0]]){
                msg(sprintf($this->getLang('e_missingargs'),hsc($args[0]),hsc($args[1])),-1);
                continue;
            }

            // is action element?
            if($args[0] == 'action'){
                array_shift($args);
                $action['type'] = array_shift($args);
                $action['argv'] = $args;
                continue;
            }

            // is thank you text?
            if($args[0] == 'thanks'){
                $thanks = $args[1];
                continue;
            }

            // An identifier for the form
            if($args[0] == 'name'){
                $this->form_prefix = cleanID($args[1],false,true).'__';
                continue;
            }

            // show always will display the form even after action is done
            if($args[0] == 'show'){
                $show = $args[1];
                continue;
            }

            // get standard arguments
            $opt = array();
            $opt['cmd']   = array_shift($args);
            $opt['label'] = array_shift($args);
            $opt['idx']   = $idx++;

            if($opt['cmd'] == 'const'){
                $opt['idname']  = $opt['label'];
                $opt['default'] = array_shift($args);
            }

            //this is to allow multiple submit with different name in a form
            if($opt['cmd'] == 'submit'){
                $opt['optional'] = true;
            }

            // save addtional minimum args here
            $keep = $this->argcheck[$opt['cmd']]-2;
            if($keep > 0){
                $opt['args'] = array_slice($args,0,$keep);
            }

            // parse additional arguments
            foreach($args as $arg){
                //echo $arg."\n";
                if($arg[0] == '='){
                    $opt['default'] = substr($arg,1);
                }elseif($arg[0] == '>'){
                    $opt['min'] = substr($arg,1);
                    if(!is_numeric($opt['min']) && !$this->_date_isvalid($opt['min'])) unset($opt['min']);
                    //TODO: error msg
                }elseif($arg[0] == '<'){
                    $opt['max'] = substr($arg,1);
                    if(!is_numeric($opt['max']) && !$this->_date_isvalid($opt['min'])) unset($opt['max']);
                    //TODO: error msg
                }elseif($arg[0] == '/' && substr($arg,-1) == '/'){
                    $opt['re'] = substr($arg,1,-1);
                }elseif($arg == '!'){
                    $opt['optional'] = true;
                }elseif($arg == '@'){
                    $opt['pagename'] = true;
                }elseif(preg_match('/x\d/', $arg)) {
                    $opt['rows'] = substr($arg,1);
                }elseif($arg[0] == '#'){
                    $opt['maxlen'] = substr($arg,1); //SCH: optional max char of input len
                    if(!is_numeric($opt['maxlen'])) unset($opt['maxlen']);
                    //TODO: error msg
                }elseif($arg[0] == '&'){
                    $opt['idname'] = cleanID(substr($arg,1),false,true); //SCH: optional element name
                }
            }
            $cmds[] = $opt;

        }

        // check if specified action is available
        $action['type'] = preg_replace('/[^a-z]+/','',$action['type']);
        if($action['type'] && !@file_exists(DOKU_PLUGIN.'bureaucracy/actions/' . $action['type'] . '.php')) {
            msg(sprintf($this->getLang('e_noaction'), $action),-1);
        }
        // set thank you message
        if(!$thanks){
            $thanks = $this->getLang($action['type'].'_thanks');
        }else{
            $thanks = hsc($thanks);
        }
        return array('data'=>$cmds,'action'=>$action,'thanks'=>$thanks,'name'=>$this->form_prefix,'show'=>$show);
    }

   /**
     * Create the form
     */
    function _htmlform($data,$errors,$reset=false,$forced_post=NULL){
        global $ID;

        $upload_ns = $this->getConf('upload_namespace');
        if(!$upload_ns) $upload_ns = getNS($ID);

        $form = new Doku_Form('bureaucracy__plugin',wl($ID).'#'.$this->form_prefix,'post');
        //$form = new Doku_Form('bureaucracy__plugin',wl($ID).'#'.$this->form_prefix,'post',$enctype='multipart/form-data');
        $form->addHidden('id',$ID);

        $captcha = false; // to make sure we add it only once

        foreach($data as $opt){
            $k=$opt['idx'];
            if(isset($opt['idname'])) $k=$opt['idname'];
            $name = $this->form_prefix.'['.$k.']';

            $value = '';
            if(is_null($forced_post)) $forced_post = $_POST;
            if(!$reset && isset($forced_post[$this->form_prefix][$k])){
                $value = $forced_post[$this->form_prefix][$k];
            }else{
                $value = $opt['default'];
            }
            if(isset($opt['maxlen'])) $value = substr(trim(cleanText($value)),0,$opt['maxlen']);
            //msg($name.'='.$value.' : '.$this->form_prefix.'['.$k.'] ='.$forced_post[$this->form_prefix][$k],0);

            $class = '';
            if($errors[$k]){
                if(isset($this->classes['optional']))
                    $class .= $this->classes['error']; //'bureaucracy_error'
            }

            if(isset($opt['optional']) && $opt['optional']){
                if(isset($this->classes['optional']))
                    $class .= ' '.$this->classes['optional'];
            } else if(isset($this->classes['required'])) {
                $class .= ' '.$this->classes['required'];
            }

            // we always start with a fieldset!
            if(!$form->_infieldset && $opt['cmd'] != 'fieldset'){
                $form->startFieldset('');
            }

            // handle different field types
            switch($opt['cmd']){
                case 'name':
                    $form->addHidden($opt['idname'],$opt['default']);
                    break;
                case 'const':
                    $form->addHidden($name,$value);
                    break;
                case 'fieldset':
                    $form->startFieldset($opt['label']);
                    break;
                case 'submit':
                    //add captcha if available
                    if(!$captcha){
                        $captcha = true;
                        $helper = plugin_load('helper','captcha');
                        if(!is_null($helper) && $helper->isEnabled()){
                            $form->addElement($helper->getHTML());
                        }
                    }

                    $form->addElement(form_makeButton('submit','', $opt['label'],array('name'=>$name)));
                    break;
                case 'password':
                    $form->addElement(form_makePasswordField($name,$opt['label'],'',$class));
                    break;
                case 'date':
                    $form->addElement('<label class="'.$class.'"><span>'.hsc($opt['label']).'</span><span><input type="text" class="edit datepicker" name="'.$name.'" id="'.$name.'"  value="'.hsc($value).'" /></span></label><script type="text/javascript"><!--//--><![CDATA[//><!--
buroDatePicker.addBtnActionPicker("'.$name.'","");
//--><!]]></script>');

                    break;
                case 'textbox':
                case 'number':
                case 'email':
                case 'phone':
                case 'link':
                    $form->addElement(form_makeTextField($name,$value,$opt['label'],'',$class));
                    break;
                case 'file':
                    if(!$value) $value=$this->getLang('no_javascript');

                    //$form->addElement('<label class="'.$class.'"><span>'.hsc($opt['label']).'</span>'.'<textarea readonly="readonly" id="'.$name.'" row="1" class="edit fileinput" name="'.$name.'">'.$value.'</textarea></label><script type="text/javascript"><!--//--><![CDATA[//><!--
                    $form->addElement('<label class="'.$class.'"><span>'.hsc($opt['label']).'</span><span><input type="text" readonly="readonly" id="'.$name.'" row="1" class="edit fileinput" name="'.$name.'" value="'.$value.'" /></span></label><script type="text/javascript"><!--//--><![CDATA[//><!--
media2.fillonload("'.$name.'","'.$upload_ns.'");
//--><!]]></script>');
                    //TODO: put call to fillonload in script.js: find a way to call fillonload after form is created and pass options to it from php [JSON?]
                    break;
                case 'onoff':
                case 'yesno':
                    $chk = ($value) ? 'checked="checked"' : '';
                    $form->addElement('<label class="'.$class.'"><span>'.hsc($opt['label']).'</span>'.
                                      '<input type="checkbox" name="'.$name.'" value="Yes" '.$chk.' /></label>');
                    break;
                case 'leftcheck':
                    $chk = ($value) ? 'checked="checked"' : '';
                    $form->addElement('<label class="bureaucracy_leftcheck"><input type="checkbox" name="'.$name.'" value="Yes" '.$chk.' /><span>'.$opt['label'].'</span></label>');
                    break;
                case 'select':
                    $vals = explode('|',$opt['args'][0]);
                    $vals = array_map('trim',$vals);
                    $vals = array_filter($vals);
                    array_unshift($vals,' ');
                    $form->addElement(form_makeListboxField($name,$vals,$value,$opt['label'],'',$class));
                    break;
                case 'static':
                    $form->addElement('<p>'.hsc($opt['label']).'</p>');
                    break;
                case 'textarea':
                    $rows = ($opt['rows']) ? $opt['rows'] : 10;
                    $form->addElement('<label class="'.$class.'"><span>'.hsc($opt['label']).'</span>'.
                                      '<textarea name="'.$name.'" rows="' . $rows . '" cols="10" class="edit">'.$value.'</textarea></label>');
                    break;
            }
        }

        ob_start();
        echo '<a name="'.hsc($this->form_prefix).'"></a>';
        $form->printForm();
        $out .= ob_get_contents();
        ob_end_clean();
        return $out;
    }

    /**
     * Validate any posted data, display errors using the msg() function,
     * put a list of bad fields in the return array
     */
    function _checkpost($data){
        $errors = array();

        foreach($data as $opt){
            // required
            $optkey = $opt['idx'];
            if(isset($opt['idname'])) $optkey = $opt['idname'];
            if(trim($_POST[$this->form_prefix][$optkey]) === ''){
                if($opt['optional']) continue;
                if(in_array($opt['cmd'],$this->nofield)) continue;
                $errors[$optkey] = 1;
                msg(sprintf($this->getLang('e_required'),hsc($opt['label'])),-1);
                continue;
            }

            $value = $_POST[$this->form_prefix][$optkey];

            // required file
            if($opt['cmd']=='file' && ($value==='' || $value[0]!=":")){
                if($opt['optional']) continue;
                $errors[$optkey] = 1;
                msg(sprintf($this->getLang('e_required'),hsc($opt['label'])),-1);
                continue;
            }

            if(isset($opt['maxlen']) && strlen($value) > $opt['maxlen']) {
                $errors[$optkey] = 1;
                msg(sprintf($this->getLang('e_toolong'),hsc($opt['label']),hsc($opt['maxlen'])),-1);
                continue;
            }

            // regexp
            if($opt['re'] && !@preg_match('/'.$opt['re'].'/i',$value)){
                $errors[$optkey] = 1;
                msg(sprintf($this->getLang('e_match'),hsc($opt['label']),hsc($opt['re'])),-1);
                continue;
            }

            // email
            if($opt['cmd'] == 'email' && !mail_isvalid($value)){
                $errors[$optkey] = 1;
                msg(sprintf($this->getLang('e_email'),hsc($opt['label'])),-1);
                continue;
            }

            // phone
            if($opt['cmd'] == 'phone' && !$this->_phone_isvalid($value)){
                $errors[$optkey] = 1;
                msg(sprintf($this->getLang('e_phone'),hsc($opt['label'])),-1);
                continue;
            }

            // link
            if($opt['cmd'] == 'link' && !$this->_link_isvalid($value)){
                $errors[$optkey] = 1;
                msg(sprintf($this->getLang('e_link'),hsc($opt['label'])),-1);
                continue;
            }

            if($opt['cmd'] == 'file' && !$this->_file_isvalid($value)){
                $errors[$optkey] = 1;
                msg(sprintf($this->getLang('e_file'),hsc($opt['label'])),-1);
                continue;
            }

            // date
            if($opt['cmd'] == 'date') {
                if(!$this->_date_isvalid($value)){
                    $errors[$optkey] = 1;
                    msg(sprintf($this->getLang('e_date'),hsc($opt['label'])),-1);
                    continue;
                } else {
                    $absdate = strtotime($value);
                    if (isset($opt['min'])) {
                        $absdatemin = strtotime($opt['min']);
                        if ($absdate < $absdatemin) {
                            $errors[$optkey] = 1;
                           msg(sprintf($this->getLang('e_min_date'),hsc($opt['label']),hsc($opt['min'])),-1);
                            continue;
                        }
                    }
                    if (isset($opt['max'])) {
                        $absdatemax = strtotime($opt['max']);
                        if ($absdate > $absdatemax) {
                            $errors[$optkey] = 1;
                            msg(sprintf($this->getLang('e_max_after'),hsc($opt['label']),hsc($opt['max'])),-1);
                            continue;
                        }
                    }
                }
            }

            // numbers
            if($opt['cmd'] == 'number' && !is_numeric($value)){
                $errors[$optkey] = 1;
                msg(sprintf($this->getLang('e_numeric'),hsc($opt['label'])),-1);
                continue;
            }

            // min
            if($opt['cmd'] != 'date' && isset($opt['min']) && $value < $opt['min']){
                $errors[$optkey] = 1;
                msg(sprintf($this->getLang('e_min'),hsc($opt['label']),hsc($opt['min'])),-1);
                continue;
            }

            // max
            if($opt['cmd'] != 'date' && isset($opt['max']) && $value > $opt['max']){
                $errors[$optkey] = 1;
                msg(sprintf($this->getLang('e_max'),hsc($opt['label']),hsc($opt['max'])),-1);
                continue;
            }

        }

        return $errors;
    }

    /**
     * Create output
     */
    function _action($data) {
        $errors = array();
        $reset = true;
        if(isset($_POST['bureaucracy__'.$this->form_prefix])){
            $errors = $this->_checkpost($data['data']);
            // check CAPTCHA
            $ok = true;
            $helper = plugin_load('helper','captcha');
            if(!is_null($helper) && $helper->isEnabled()){
                $ok = $helper->check();
            }

            $reset = false;
            $doc = '';
            if($ok && !count($errors) && $data['action']){
                require_once(DOKU_PLUGIN . 'bureaucracy/actions/actions.php');
                require_once(DOKU_PLUGIN . 'bureaucracy/actions/' . $data['action']['type'] . '.php');
                $class = 'syntax_plugin_bureaucracy_action_' . $data['action']['type'];
                $action = new $class();

                $success = $action->run($data['data'], $data['thanks'], $data['action']['argv'], $errors);
                if($success) {
                    $doc .= '<div class="bureaucracy__plugin" id="scroll__here">';
                    $doc .= $success;
                    $doc .= '</div>';
                    $reset = true;
                }

            }
        }

        $showform = false;
        if (isset($data['show']) && $data['show'] == 'always') $showform = true;
        if (!$doc || $showform) $doc .= $this->_htmlform($data['data'],$errors,$reset);
        return $doc;
    }

    /**
     * Collect posted form data in an k=>v array
     */
    function _collectpost($data,$keepPOST=false,$keepGET=false){
        $values = array();
        $allkeys = array();
        if ($keepPOST) $allkeys = array_merge($allkeys,$_POST);
        if ($keepGET)  $allkeys = array_merge($allkeys,$_GET);
        $name = $this->form_prefix;
        foreach ($data as $opt) {
            if (!in_array($opt['cmd'],$this->nofield)) {
                $k = $opt['idx'];
                if (isset($opt['idname'])) $k = $opt['idname'];
                $v = $opt['default'];
                if (isset($_POST[$name][$k])) {
                    $v = $_POST[$name][$k];
                    if ($opt['cmd']=='file' && $v[0]!=":") $v='';
                } else {
                    if ($opt['cmd']=='onoff') $v='off';
                    if ($opt['cmd']=='yesno') $v='no';
                    if ($opt['cmd']=='leftcheck') $v='no';
                }
                $values[$k] = $v;
                if(isset($allkeys[$name][$k])) unset(
                    $allkeys[$name][$k]);
            }
        }
        $values = array_merge($allkeys,$values);
        return $values;
    }

    /**
      * Reverse operation of _collectpost
      */
    function _idnames2idx($data,$values) {
        $values2 = array();
        foreach ($data as $opt) {
            if (!in_array($opt['cmd'],$this->nofield)) {
                $optkey = $opt['idx'];
                if (isset($opt['idname'])) $optkey = $opt['idname'];
                $v = '';
                if (isset($opt['default']))  $v = $opt['default'];
                if (isset($values[$optkey])) $v = $values[$optkey];
                $values2[$this->form_prefix][$optkey] = $v;
            }
        }
        return $values2;
    }

}
// vim:ts=4:sw=4:et:enc=utf-8:
